#include"symtable.h"
#include"def.h"
using namespace std;
int level=0;
symtable* mytable=new symtable();
sym::sym(string name,int level,string type,char flag,int paramnum
                    ,int strong,int lineno,int dimension):
                name(name),level(level),type(type),flag(flag),paramnum(paramnum)
               ,strong(strong),lineno(lineno),dimension(dimension),arraySize(0){
  
}

string sym::toString(){
  return flag=='F'?name:"v"+to_string(index);
}

void sym::display(int index){
  string real_type = type;
  for(int i=0;i<dimension;i++){
    real_type += "[";
    if(degree[i]!=-1){
      real_type += to_string(degree[i]);
    }
    real_type += "]";
  }
  printf("%d\t%s\t%d\t%s\t%c",index,name.c_str(),level,real_type.c_str(),flag);
  if(paramnum>=0)printf("\t%d",paramnum);
#ifdef PRINT_STRONG
  if(paramnum>=0)printf("\t%d",strong);
  else printf("\t\t%d",strong);
#endif
  printf("\n");
}


symtable::symtable():index(0),level(0){

}

sym* symtable::find(string name){
  if(!symbols.empty()){
    for(int i=symbols.size()-1;i>=0;i--){
      if(symbols[i]->name == name){
        return symbols[i];
      }
    }
    return NULL;
  }
  return NULL;
}

int symtable::find(sym* id,int del){
  string name = id->name;
  if(!symbols.empty()){
    int num = -1;
    for(int i=symbols.size()-1;i>=0;i--){
      if(symbols[i]==id){
        num = i;
        continue;
      }
      if(symbols[i]->name == name){
        int errorType = 1;
        if(symbols[i]->level!=id->level){
          continue;
        }
        if(symbols[i]->flag != 'F'&&id->flag != 'F'){
          if(id->level==0 && (!symbols[i]->strong || !id->strong)){
            continue;
          }
        }
        if(id->type!=symbols[i]->type){
          errorType=2;
        }
        if(del){
          if(num==-1){
            return errorType;
          }else
            symbols.erase(symbols.begin()+num);
        }
        return errorType;
      }
    }
    return 0;
  }
  return 0;
}

void symtable::add(sym* e){
    symbols.push_back(e);
    index++;
    e->index = index;
#ifdef PRINT_SYMTABLE
    display();
#endif
}

void symtable::addlist(vector<sym*> list){
  for(sym* i:list){
    symbols.push_back(i);
    index++;
    i->index = index;
  }
#ifdef PRINT_SYMTABLE
  display();
#endif
}


void symtable::pop(int level){
  int popflag=0;
  while(!symbols.empty()&&symbols.back()->level==level){
    popflag=1;
    index--;
    symbols.pop_back();
  }
#ifdef PRINT_SYMTABLE
  if(popflag){
    display();
  }
#endif
}



void symtable::display(){
  printf("----------------symbol table-----------------------\n");
  printf("Index\tName\tLevel\tType\tFlag\tParam_num\n");
  printf("---------------------------------------------------\n");
  for(int i=0;i<index;i++){
      symbols[i]->display(i);
  }
  printf("---------------------------------------------------\n");
}

symDriver::symDriver(){

}

symDriver::symDriver(driver* myDriver):driver(myDriver),id(NULL),level(0){
  switch(myDriver->getNode()->nodeType){
    case SYM_FUNCDEF     :driver::addcal({FUNCDEF_sym}); 
                          driver::addop({level_pass,add_sym});       break;
    case SYM_PARAM       :driver::addcal({PARAM_sym});   
                          driver::addop({level_pass,add_sym});       break;                
    case SYM_CONSTDECL   :driver::addcal({DECL_sym});                              
                          driver::addop({level_pass,addlist_sym});   break;
    case SYM_VARDECL     :driver::addcal({DECL_sym});            
                          driver::addop({level_pass,addlist_sym});   break;
    case SYM_BLOCK       :driver::addop({level_inc});                
                          driver::addexit({level_dec});              break;
    default              :driver::addop({level_pass});
  }
}

symDriver* symDriver::getSymDriver(driver* myDriver){
  driver *iDriver = myDriver;
  symDriver *iSymDriver = NULL;
  while(iSymDriver == NULL){
    iSymDriver = dynamic_cast<symDriver*>(iDriver);
    iDriver = iDriver->myDriver;
    if(iDriver==NULL&&iSymDriver==NULL)return NULL;
  }
  return iSymDriver;
}

void symDriver::correspond(){
  if(id){
    id->level=level;
    if(myDriver->getNode()->nodeType==SYM_PARAM){
      id->level++;
    }
  }
  for(sym* cid:ids){
    cid->level=level;
    if(myDriver->getNode()->nodeType==SYM_PARAM){
      cid->level++;
    }
  }
}



string param_type(node* param){
  string type="";
  type+=param->children[0]->getName();
  for(int i=0;i<param->children.size()-2;i++){type+="[]";}
  return type;
}

int dimension_type(sym* id,node* def){
  id->arraySize = 1;
  for(node* i:def->children){
    if(i->nodeType == SYM_BRAKET){
      if(i->children.empty()){
         id->degree.push_back(-1);
         id->arraySize = -1;
      }else{
         if(i->children[0]->cal_type!=0){
           id->degree.push_back(-1);
           id->arraySize = -1;
         }else{
           id->arraySize *= i->children[0]->cal_int;
           id->degree.push_back(i->children[0]->cal_int);
         }
      }
    }
  }
  return 1;
}

int PARAM_sym(driver* originDriver){
  node* def = originDriver->getNode();
  symDriver *myDriver = (symDriver*)originDriver;
  myDriver->id = new sym(*(def->children[1]->type_val),
                   level,def->children[0]->getName(),'P',-1,
                   1,def->children[1]->lineno,def->dimension);
  dimension_type(myDriver->id,def);
  return 1;
}

int FUNCDEF_sym(driver* originDriver){
  string type="";
  node* def = originDriver->getNode();
  symDriver *myDriver = (symDriver*)originDriver;
  int paramnum=0;
  type+=def->children[0]->children[0]->getName();
  type+="(";
  if(def->children.size()>2){
    for(node*i:def->children[1]->children){
      if(i!=def->children[1]->children[0])type+=",";
      type+=param_type(i);
    }
    type+=")";
    paramnum=def->children[1]->children.size();
  }else{
    type+="void)";
  }
  myDriver->id = new sym(*(def->children[0]->children[1]->type_val),
                   level,type,'F',paramnum,
                   1,def->children[0]->children[1]->lineno,0);
  return 1;
}

int DECL_sym(driver* originDriver){
  node* decl = originDriver->getNode();
  symDriver *myDriver = (symDriver*)originDriver;
  string typeBase = "";
  if(decl->nodeType==SYM_CONSTDECL)typeBase+="const ";
  typeBase+=decl->children[0]->getName();
  for(node* i:decl->children){
    if(i->nodeType==SYM_CONSTDEF||i->nodeType==SYM_VARDEF){
      int strong = i->children.back()->nodeType==SYM_INITVAL ||
                   i->children.back()->nodeType==SYM_CONSTINITVAL;
      sym* id = new sym(*(i->children[0]->type_val),
                        level,typeBase,'V',-1,
                        1,i->children[0]->lineno,
                        i->dimension);
      dimension_type(id,i);
      myDriver->ids.push_back(id);
    }
  }
  return 1;
}

int level_pass(driver* originDriver){
  symDriver *myDriver = (symDriver*)originDriver;
  node* n = myDriver->getNode();
  for(node* i:n->children){
    driver *iDriver = i->myDriver;
    symDriver *iSymDriver = symDriver::getSymDriver(iDriver);
    if(iSymDriver==NULL)return -1;
    iSymDriver->level = myDriver->level;
    iSymDriver->correspond();
  }
  return 1;
}

int level_inc(driver* originDriver){
  symDriver *myDriver = (symDriver*)originDriver;
  node* n = myDriver->getNode();
  for(node* i:n->children){
    driver *iDriver = i->myDriver;
    symDriver *iSymDriver = symDriver::getSymDriver(iDriver);
    if(iSymDriver==NULL)return -1;
    iSymDriver->level = myDriver->level+1;
    iSymDriver->correspond();
  }
  return 1;
}

int level_dec(driver* originDriver){
  symDriver *myDriver = (symDriver*)originDriver;
  mytable->pop(myDriver->level+1);
  return 1;
}

int add_sym(driver* originDriver){
  symDriver *myDriver = (symDriver*)originDriver;
  mytable->add(myDriver->id);
  return 1;
}

int addlist_sym(driver* originDriver){
  symDriver *myDriver = (symDriver*)originDriver;
  mytable->addlist(myDriver->ids);
  return 1;
}
